#ifndef SERIALPORT_H
#define SERIALPORT_H

#include <string>
#include <vector>

struct termios;
namespace sh
{

/*
 * @brief   linux串口通信封装
 * @author  haitao.lin
 * @date    2021-07-25
 */
class SerialPort
{
public:
    enum BaudRate
    {
        BR0 = 0000000,
        BR50 = 0000001,
        BR75 = 0000002,
        BR110 = 0000003,
        BR134 = 0000004,
        BR150 = 0000005,
        BR200 = 0000006,
        BR300 = 0000007,
        BR600 = 0000010,
        BR1200 = 0000011,
        BR1800 = 0000012,
        BR2400 = 0000013,
        BR4800 = 0000014,
        BR9600 = 0000015,
        BR19200 = 0000016,
        BR38400 = 0000017,
        BR57600 = 0010001,
        BR115200 = 0010002,
        BR230400 = 0010003,
        BR460800 = 0010004,
        BR500000 = 0010005,
        BR576000 = 0010006,
        BR921600 = 0010007,
        BR1000000 = 0010010,
        BR1152000 = 0010011,
        BR1500000 = 0010012,
        BR2000000 = 0010013,
        BR2500000 = 0010014,
        BR3000000 = 0010015,
        BR3500000 = 0010016,
        BR4000000 = 0010017
    };

    enum DataBits
    {
        DataBits5,
        DataBits6,
        DataBits7,
        DataBits8,
    };

    enum StopBits
    {
        StopBits1,
        StopBits2
    };

    enum Parity
    {
        ParityNone,
        ParityEven,
        PariteMark,
        ParityOdd,
        ParitySpace
    };

    struct OpenOptions
    {
        BaudRate baudRate; //波特率
        DataBits dataBits; //数据位
        StopBits stopBits; //停止位
        Parity parity;     //奇偶校验
        bool xon;
        bool xoff;
        bool xany;
        int vmin;  //每次获取的最小数据量
        int vtime; //等待最小数据量的超时时间
    };

public:
    /*
     * @brief   获取/dev/tty*，不一定是串口
     * @date    2021-07-24
     */
    static std::vector<std::string> list();

    /*
     * @brief   指定串口是否存在，可以在使用中检测，如果消失，则可能是设备断开
     */
    static bool exists(const std::string &serialName);

public:
    explicit SerialPort(const std::string &path);
    virtual ~SerialPort();

    /**
     * 使用设置好的参数打开串口，返回值表示是否成功打开
     */
    bool open();

    /**
     * 使用指定的参数打开串口，并覆盖配置参数，返回值表示是否成功打开
     */
    bool open(const OpenOptions &options);

    /**
     * 返回串口是否打开
     */
    bool isOpen() const;

    /**
     * 发送数据
     */
    int write(const void *data, int length);

    /*
     * 读取最大指定长度的数据，返回读出的实际长度，<=0表示未读到或者有错误
     */
    int read(void *data, int length);

    /**
     * overload，无法返回是否读到
     */
    std::string read(int length);

    /**
     * 关闭串口
     */
    void close();

    /*
     * @brief   设置阻塞/非阻塞，如果不设置，默认非阻塞，需要再open成功自后调用
     */
    void setReadBlock(bool block);

    /*
     * @brief   设置/获取波特率
     */
    void setBaudRate(BaudRate baudRate);
    BaudRate baudRate() const;

    /*
     * @brief   设置/获取停止位
     */
    void setStopBits(StopBits stopBits);
    StopBits stopBits() const;

    /*
     * @brief   设置/获取数据位
     */
    void setDataBits(DataBits dataBits);
    DataBits dataBits() const;

    /*
     * @brief   设置/获取奇偶校验位
     */
    void setParity(Parity parity);
    Parity parity() const;

    /*
     * @brief   设置/获取读取的最小字符数量，需要再open之前设置
     *          只有设置为阻塞时才有效，仅针对于读操作，详细见vtime说明
     */
    void setReadMinSize(std::size_t vmin);
    std::size_t readMinSize() const;

    /*
     * @brief   设置/获取读取超时，需要再open之前设置，单位百毫秒
     *          只有设置为阻塞时才有效，仅针对于读操作
     * @note    以下说明和read的第三个参数n有关
     *          当vmin=0，vtime=0时，阻塞无效；
     *          当vim=0, vtime>0时，阻塞时间为vtime（百毫秒）
     *          当vim>0，vtime=0时，缓存区数据<vim，阻塞，缓存区数据>=vim，先读出min(vim, n)，剩下的每次读取n；
     *
     *          当vtime不为0时，若vim<n，则vim被设置成n
     */
    void setReadTimeout(std::size_t vtime);
    std::size_t readTimeout() const;

protected:
    void termiosOptions(termios &tios, const OpenOptions &options);

private:
    std::string _path;
    OpenOptions _open_options;
    int _tty_fd;
    bool _is_open;
};
}
bool operator==(const sh::SerialPort::OpenOptions &lhs, const sh::SerialPort::OpenOptions &rhs);
bool operator!=(const sh::SerialPort::OpenOptions &lhs, const sh::SerialPort::OpenOptions &rhs);


#endif // SERIALPORT_H
